#include "e.h"
#include "e_mod_main.h"
#include "compiz.h"
#include "bang.h"
#include "bang_window.h"

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/Xcomposite.h>


//# define D(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define D(x)  ((void) 0)

//# define DN(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define DN(x)  ((void) 0)

//# define DN(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define DE(x)  ((void) 0)

static int
reallocWindowPrivates (int  size,
                       void *closure)
{
D(("BEGIN\n"));
   CompScreen *s = (CompScreen *) closure;
   CompWindow *w;
   void       *privates;
   Eina_List *l;
   
   for (l = s->windows; l; l = l->next)
   {
      w = l->data;
      privates = realloc (w->privates, size * sizeof (CompPrivate));
      if (!privates)
         return FALSE;
      
      w->privates = (CompPrivate *) privates;
   }
   
   return TRUE;
}

int
allocateWindowPrivateIndex (CompScreen *screen)
{
D(("BEGIN\n"));
    return allocatePrivateIndex (&screen->windowPrivateLen,
				 &screen->windowPrivateIndices,
				 reallocWindowPrivates,
				 (void *) screen);
}

void
freeWindowPrivateIndex (CompScreen *screen,
			int	   index)
{
D(("BEGIN\n"));
    freePrivateIndex (screen->windowPrivateLen,
		      screen->windowPrivateIndices,
		      index);
}


static void
setWindowMatrix (CompWindow *w)
{
DN(("setWindowMatrix \n"));
    w->matrix = w->texture.matrix;
    w->matrix.x0 -= (w->attrib.x * w->matrix.xx);
    w->matrix.y0 -= (w->attrib.y * w->matrix.yy);
}

void
bindWindow (CompWindow *w)
{
D(("BEGIN bindWindow (CompWindow *w)\n"));
   redirectWindow (w);

   if (!w->pixmap)
   {
      //XWindowAttributes attr;
      Ecore_X_Window_Attributes attr;
      /* We have to grab the server here to make sure that window
      is mapped when getting the window pixmap */
      
      ecore_x_grab();
      ecore_x_window_attributes_get(w->id,&attr);
      if (!attr.viewable)
      {
         ecore_x_ungrab();
         finiTexture (w->screen, &w->texture);
         w->mapNum = 0;
         return;
      }

      w->pixmap = XCompositeNameWindowPixmap (w->screen->display->display,
                                              w->id);
      ecore_x_ungrab();
   }
   D(("  bindWindow: bindPixmapToTexture (w->width=%d, w->height=%d))\n",w->width, w->height));
   if (!bindPixmapToTexture (w->screen, &w->texture, w->pixmap,
                             w->width, w->height,
                             w->attrib.depth))
   {
      fprintf (stderr, "%s: Couldn't bind redirected window 0x%x to "
               "texture\n", bang->programName, (int) w->id);
   }

   setWindowMatrix (w);
D(("END   bindWindow (CompWindow *w)\n"));
}

void
releaseWindow (CompWindow *w)
{
   DF(("releaseWindow \n"));
   if (w->pixmap)
   {
      releasePixmapFromTexture (w->screen, &w->texture);
      XFreePixmap (w->screen->display->display, w->pixmap);

      w->pixmap = None;
   }
}

static void
freeWindow (CompWindow *w)
{
   DF(("FREE WINDOW [id 0x%x]\n", w->id));
   releaseWindow(w);

   if (w->texture.name)
   {
      DF(("  finiTexture(w->texture)\n"));
      finiTexture(w->screen, &w->texture);
   }
      
   if (w->frame)
   {
      DF(("  w->frame\n"));
      XDestroyWindow(w->screen->display->display, w->frame);
   }
   if (w->clip)
   {
      DF(("  w->clip\n"));
      XDestroyRegion(w->clip);
   }
   if (w->region)
   {
      DF(("  w->region\n"));
      XDestroyRegion(w->region);
   }
   if (w->privates)
   {
      DF(("  w->privates\n"));
      free(w->privates);
   }
   if (w->sizeDamage)
   {
      DF(("w->damageRects\n"));
      free(w->damageRects);
   }
   if (w->vertices)
   {
      DF(("  w->vertices\n"));
      free(w->vertices);
   }
   if (w->indices)
   {
      DF(("  w->indices\n"));
      free(w->indices);
   }
   if (w->name)
   {
      DF(("  w->name\n"));
      free(w->name);
   }
   
   
   if (lastFoundWindow == w)
   {
      lastFoundWindow = 0;
   }
   if (lastDamagedWindow == w)
   {
      lastDamagedWindow = 0;
   }
   
   DF(("  free(w)\n"));
   E_FREE(w);
}

void
damageWindowRegion (CompWindow *w,
		    Region     region)
{
DN(("BEGIN\n"));
    if (w->scaled)
    {
	REGION reg;
	int    x1, y1, x2, y2;

	reg.rects    = &reg.extents;
	reg.numRects = 1;

	x1 = region->extents.x1 - w->attrib.x;
	y1 = region->extents.y1 - w->attrib.y;
	x2 = region->extents.x2 - w->attrib.x;
	y2 = region->extents.y2 - w->attrib.y;

	reg.extents.x1 = (x1 * w->paint.xScale) + w->attrib.x;
	reg.extents.y1 = (y1 * w->paint.yScale) + w->attrib.y;
	reg.extents.x2 = (x2 * w->paint.xScale + 0.5f) + w->attrib.x;
	reg.extents.y2 = (y2 * w->paint.yScale + 0.5f) + w->attrib.y;

	if (reg.extents.x2 > reg.extents.x1 && reg.extents.y2 > reg.extents.y1)
	    damageScreenRegion (w->screen, &reg);
    }
    else
    {
	damageScreenRegion (w->screen, region);
    }
}

Bool
damageWindowRect (CompWindow *w,
		  Bool       initial,
		  BoxPtr     rect)
{
//D(("BEGIN\n"));
    return FALSE;
}

void
addWindowDamage (CompWindow *w)
{
D(("   addWindowDamage \n"));
    if (w->screen->damageMask & COMP_SCREEN_DAMAGE_ALL_MASK)
	return;

    if (w->shaded || (w->attrib.map_state == IsViewable && w->damaged))
    {
	REGION region;

	region.extents.x1 = -w->output.left - w->attrib.border_width;
	region.extents.y1 = -w->output.top - w->attrib.border_width;
	region.extents.x2 = w->width + w->output.right;
	region.extents.y2 = w->height + w->output.bottom;

	if (!(*w->screen->damageWindowRect) (w, FALSE, &region.extents))
	{
	    region.extents.x1 += w->attrib.x + w->attrib.border_width;
	    region.extents.y1 += w->attrib.y + w->attrib.border_width;
	    region.extents.x2 += w->attrib.x + w->attrib.border_width;
	    region.extents.y2 += w->attrib.y + w->attrib.border_width;

	    region.rects = &region.extents;
	    region.numRects = region.size = 1;

	    damageWindowRegion (w, &region);
	}
    }
}

void
updateWindowRegion (CompWindow *w)
{
D(("updateWindowRegion\n"));
    REGION     rect;
    XRectangle r, *rects, *shapeRects = 0;
    int	       i, n = 0;

    EMPTY_REGION (w->region);

    if (w->screen->display->shapeExtension)
    {
	int order;

	shapeRects = XShapeGetRectangles (w->screen->display->display, w->id,
					  ShapeBounding, &n, &order);
    }

    if (n < 2)
    {
	r.x      = -w->attrib.border_width;
	r.y      = -w->attrib.border_width;
	r.width  = w->width;
	r.height = w->height;

	rects = &r;
	n = 1;
    }
    else
    {
	rects = shapeRects;
    }

    rect.rects = &rect.extents;
    rect.numRects = rect.size = 1;

    for (i = 0; i < n; i++)
    {
	rect.extents.x1 = rects[i].x + w->attrib.border_width;
	rect.extents.y1 = rects[i].y + w->attrib.border_width;
	rect.extents.x2 = rect.extents.x1 + rects[i].width;
	rect.extents.y2 = rect.extents.y1 + rects[i].height;

	if (rect.extents.x1 < 0)
	    rect.extents.x1 = 0;
	if (rect.extents.y1 < 0)
	    rect.extents.y1 = 0;
	if (rect.extents.x2 > w->width)
	    rect.extents.x2 = w->width;
	if (rect.extents.y2 > w->height)
	    rect.extents.y2 = w->height;

	if (rect.extents.y1 < rect.extents.y2 &&
	    rect.extents.x1 < rect.extents.x2)
	{
	    rect.extents.x1 += w->attrib.x;
	    rect.extents.y1 += w->attrib.y;
	    rect.extents.x2 += w->attrib.x;
	    rect.extents.y2 += w->attrib.y;

	    XUnionRegion (&rect, w->region, w->region);
	}
    }

    if (shapeRects)
	XFree (shapeRects);
}

static void
setDefaultWindowAttributes (XWindowAttributes *wa)
{
D(("BEGIN\n"));
    wa->x		      = 0;
    wa->y		      = 0;
    wa->width		      = 1;
    wa->height		      = 1;
    wa->border_width	      = 0;
    wa->depth		      = 0;
    wa->visual		      = NULL;
    wa->root		      = None;
    wa->class		      = InputOnly;
    wa->bit_gravity	      = NorthWestGravity;
    wa->win_gravity	      = NorthWestGravity;
    wa->backing_store	      = NotUseful;
    wa->backing_planes	      = 0;
    wa->backing_pixel	      = 0;
    wa->save_under	      = FALSE;
    wa->colormap	      = None;
    wa->map_installed	      = FALSE;
    wa->map_state	      = IsUnviewable;
    wa->all_event_masks	      = 0;
    wa->your_event_mask	      = 0;
    wa->do_not_propagate_mask = 0;
    wa->override_redirect     = 0;
    wa->screen		      = NULL;
}

CompWindow *
addWindow (CompScreen *screen,
           Window     id,
           Window     aboveId)
{
D(("-------------------------- BEGIN addWindow -[id: %x]--------------------------\n", id));
   CompWindow *w;
   //TODO check if id just exist
   w = E_NEW(CompWindow, 1);
   if (!w)
      return NULL;

   w->mapNum    = 0;
   w->name = NULL;
   w->frame = None;

   w->shaded = FALSE;
   w->showed = FALSE;

   initTexture (screen, &w->texture);

   w->screen     = screen;
   w->pixmap     = None;
   w->destroyed  = FALSE;
   w->damaged    = FALSE;
   w->redirected = TRUE;

   w->destroyRefCnt = 1;
   w->unmapRefCnt   = 1;

   w->damageRects = 0;
   w->sizeDamage  = 0;
   w->nDamage     = 0;

   w->vertices   = 0;
   w->vertexSize = 0;
   w->indices    = 0;
   w->indexSize  = 0;
   w->vCount     = 0;

   w->input.left   = 0;
   w->input.right  = 0;
   w->input.top    = 0;
   w->input.bottom = 0;

   w->output.left   = 0;
   w->output.right  = 0;
   w->output.top    = 0;
   w->output.bottom = 0;

   w->paint.opacity    = w->opacity    = OPAQUE;
   w->paint.brightness = w->brightness = 0xffff;
   w->paint.saturation = w->saturation = COLOR;
   w->paint.xScale     = 1.0f;
   w->paint.yScale     = 1.0f;

   w->scaled = FALSE;

   if (screen->windowPrivateLen)
   {
      w->privates = malloc(screen->windowPrivateLen * sizeof (CompPrivate));
      if (!w->privates)
      {
         free(w);
         return NULL;
      }
   }
   else
      w->privates = 0;

   w->region = XCreateRegion ();
   if (!w->region)
   {
      freeWindow(w);
      return NULL;
   }

   w->clip = XCreateRegion ();
   if (!w->clip)
   {
      freeWindow(w);
      return NULL;
   }

    /* Failure means that window has been destroyed. We still have to add the
       window to the window list as we might get configure requests which
       require us to stack other windows relative to it. Setting some default
       values if this is the case. */
   /* TODO DAVE is this correct to change? */
   if (!XGetWindowAttributes(screen->display->display, id, &w->attrib))
      setDefaultWindowAttributes(&w->attrib);
   
   if (!ecore_x_window_attributes_get(id,&w->e_attrib))
   {
      w->e_attrib.input_only = 1;
      w->e_attrib.x = w->e_attrib.y = w->e_attrib.w = w->e_attrib.h = 0;
      w->e_attrib.root = bang->root;
      w->e_attrib.border = 0;
      w->e_attrib.depth = 0;
      w->e_attrib.visible = 0;
      w->e_attrib.viewable = 0;
      w->e_attrib.override = 0;
      w->e_attrib.save_under = 0;
      w->e_attrib.event_mask.mine = 0;
      w->e_attrib.event_mask.all = 0;
      w->e_attrib.event_mask.no_propagate = 0;
      w->e_attrib.window_gravity = 0;
      w->e_attrib.pixel_gravity = 0;
      w->e_attrib.colormap = 0;
      w->e_attrib.visual = 0;
   }
   
   w->name = ecore_x_icccm_title_get(id);  //TODO Remove this...is just for debug
      
   w->width  = w->e_attrib.w  + w->e_attrib.border * 2;
   w->height = w->e_attrib.h + w->e_attrib.border * 2;

   w->serverX = w->e_attrib.x;
   w->serverY = w->e_attrib.y;

   w->id = id;

   w->alpha = (w->attrib.depth == 32);

 /*   if (screen->display->shapeExtension)
	XShapeSelectInput (screen->display->display, id, ShapeNotifyMask);*/

   insertWindowIntoScreen(screen, w, aboveId);

   EMPTY_REGION(w->region);

   if (!w->e_attrib.input_only)
   {
      REGION rect;

      rect.rects = &rect.extents;
      rect.numRects = rect.size = 1;

      rect.extents.x1 = w->e_attrib.x;
      rect.extents.y1 = w->e_attrib.y;
      rect.extents.x2 = w->e_attrib.x + w->width;
      rect.extents.y2 = w->e_attrib.y + w->height;

      XUnionRegion(&rect, w->region, w->region);

      w->damage = XDamageCreate(screen->display->display, id,
                                XDamageReportRawRectangles);
   }
   else
   {
      D(("addWindow: WINDOW IS InputOnly\n"));   
      w->damage = None;
      w->attrib.map_state = IsUnmapped;
   }

   w->invisible = TRUE;

   if (w->e_attrib.viewable)
   {
      w->attrib.map_state = IsUnmapped;
      mapWindow (w);
   }

   windowInitPlugins (w);

   if (w->shaded)
      resizeWindow (w,
               w->e_attrib.x, w->e_attrib.y,
               w->e_attrib.w, ++w->e_attrib.h - 1,
               w->e_attrib.border);
D(("-------------------------- END   addWindow --- %d --------------------------------\n", w->attrib.override_redirect));
   return w;
}

void
removeWindow (CompWindow *w)
{
   D(("removeWindow (CompWindow *w)\n"));
   unhookWindowFromScreen (w->screen, w);

   if (!w->redirected)
      w->screen->overlayWindowCount--;

   windowFiniPlugins(w);

   freeWindow(w);
}

void
destroyWindow (CompWindow *w)
{
   D(("destroyWindow \n"));
   w->id = 1;
   w->mapNum = 0;

   w->destroyRefCnt--;
   if (w->destroyRefCnt)
      return;

   if (!w->destroyed)
   {
      w->destroyed = TRUE;
      w->screen->pendingDestroys++;
   }
}

void
mapWindow (CompWindow *w)
{
   D(("BEGIN mapWindow\n"));
   if (w->e_attrib.input_only)
      return;

   if (w->attrib.map_state == IsViewable)
      return;

   w->unmapRefCnt = 1;

   w->attrib.map_state = IsViewable;

//    setWmState (w->screen->display, NormalState, w->id);

   w->invisible = TRUE;
   w->damaged   = FALSE;

   w->mapNum = w->screen->mapNum++;

   updateWindowRegion (w);
//    updateWindowSize (w);
//////////////////////////////////////////////////////////////////////////////////////////////////////

    
    /* been shaded */
	//~ if (!w->height)
	    //~ resizeWindow (w,
			  //~ w->attrib.x, w->attrib.y,
			  //~ w->attrib.width, ++w->attrib.height - 1,
			  //~ w->attrib.border_width);
    //}
D(("END mapWindow\n"));
}

void
unmapWindow (CompWindow *w)
{
D(("unmapWindow \n"));
   if (w->mapNum)
   {
      //	if (w->frame && !w->shaded)
      //	    XUnmapWindow (w->screen->display->display, w->frame);

      w->mapNum = 0;
   }

   w->unmapRefCnt--;
   if (w->unmapRefCnt > 0)
      return;

   if (w->attrib.map_state != IsViewable)
      return;

   addWindowDamage (w);

   w->attrib.map_state = IsUnmapped;

   w->invisible = TRUE;

   releaseWindow (w);

   if (w->shaded && w->height)
      resizeWindow (w,
                    w->attrib.x, w->attrib.y,
                    w->attrib.width, ++w->attrib.height - 1,
                    w->attrib.border_width);

}

Bool
resizeWindow(CompWindow *w,
             int         x,
             int         y,
             int         width,
             int         height,
             int         borderWidth)
{
D(("   resizeWindow\n"));
   Window frame = None;

   if (x != w->serverX)
   {
      frame      = w->frame;
      w->serverX = x;
   }
   else
      x = w->attrib.x;
   
   if (y != w->serverY)
   {
      frame      = w->frame;
      w->serverY = y;
   }
   else
      y = w->attrib.y;

   //if (frame)   //TODO DAVE is this right to remove ????
   //   XMoveWindow (w->screen->display->display, frame,
   //         w->serverX - w->input.left,
   //         w->serverY - w->input.top);

   if (w->e_attrib.w      != width  ||
       w->e_attrib.h      != height ||
       w->e_attrib.border != borderWidth)
   {
      unsigned int pw, ph, actualWidth, actualHeight, ui;
      Pixmap       pixmap = None;
      Window       root;
      Status       result;
      int          i;

      pw = width  + borderWidth * 2;
      ph = height + borderWidth * 2;


///////////////////////////////////////////// smells hacked in here ////////////////
 // redirectWindow (w);
// XCompositeRedirectWindow (w->screen->display->display, w->id,
//		      CompositeRedirectManual);

//    w->redirected = TRUE;
//    w->screen->overlayWindowCount--;

///////////////////////////////////////////////////////////////////////////////////
      if (w->mapNum && w->redirected)
      {
         pixmap = XCompositeNameWindowPixmap(w->screen->display->display, w->id);
         result = XGetGeometry(w->screen->display->display, pixmap, &root,
                               &i, &i, &actualWidth, &actualHeight,
                               &ui, &ui);

         if (!result || actualWidth != pw || actualHeight != ph)
         {
            XFreePixmap (w->screen->display->display, pixmap);
            return FALSE;
         }
      }
      else if (w->shaded)
      {
         ph = 0;
      }

      addWindowDamage (w);

      w->attrib.x            = x;
      w->attrib.y            = y;
      w->attrib.width        = width;
      w->attrib.height       = height;
      w->attrib.border_width = borderWidth;
      
      w->e_attrib.x      = x;
      w->e_attrib.y      = y;
      w->e_attrib.w      = width;
      w->e_attrib.h      = height;
      w->e_attrib.border = borderWidth;

      w->width  = pw;
      w->height = ph;

      releaseWindow(w);

      w->pixmap = pixmap;

      if (w->mapNum)
         updateWindowRegion(w);

      //	(*w->screen->windowResizeNotify) (w);

      addWindowDamage(w);

      w->invisible = WINDOW_INVISIBLE (w);

      //updateFrameWindow (w);
   }
   else if (w->e_attrib.x != x || w->e_attrib.y != y)
   {
      int dx, dy;

      dx = x - w->e_attrib.x;
      dy = y - w->e_attrib.y;

      moveWindow (w, dx, dy, TRUE, TRUE);
   }

   return TRUE;
}

void
moveWindow(CompWindow *w,
           int        dx,
           int        dy,
           Bool       damage,
           Bool       immediate)
{
D(("   moveWindow\n"));
   if (dx || dy)
   {
      if (damage)
         addWindowDamage(w);

      w->attrib.x += dx;
      w->e_attrib.x += dx;
      w->attrib.y += dy;
      w->e_attrib.y += dy;

      XOffsetRegion(w->region, dx, dy);

      setWindowMatrix(w);

      w->invisible = WINDOW_INVISIBLE(w);

      //	(*w->screen->windowMoveNotify) (w, dx, dy, immediate);

      if (damage)
         addWindowDamage(w);
   }
}


void
setWindowScale (CompWindow *w,
		float      xScale,
		float      yScale)
{
D(("setWindowScale \n"));
    if (xScale > 0.999f && xScale < 1.001f &&
	yScale > 0.999f && yScale < 1.001f)
    {
	w->paint.xScale = 1.0f;
	w->paint.yScale = 1.0f;

	w->scaled = FALSE;
    }
    else
    {
	w->paint.xScale = xScale;
	w->paint.yScale = yScale;

	w->scaled = TRUE;
    }
}

void
unredirectWindow (CompWindow *w)
{
   D(("BEGIN\n"));
   D(("******  UNREDIRECT   [w->id: %x][w->height: %d]  ****************\n", w->id, w->height));
   fflush(stdout);
   if (!w->redirected)
      return;

   releaseWindow (w);

   XCompositeUnredirectWindow (w->screen->display->display, w->id,
                               CompositeRedirectManual);

   w->redirected = FALSE;
   w->screen->overlayWindowCount++;
}

void
redirectWindow (CompWindow *w)
{
   D(("redirectWindow (CompWindow *w): %s\n",ecore_x_icccm_title_get(w->id)));
   D(("******  REDIRECT  [w->id: %x][w->height: %d]  ****************\n", w->id, w->height));
   fflush(stdout);

   if (w->redirected)
      return;

   XCompositeRedirectWindow (w->screen->display->display, w->id,
                             CompositeRedirectManual);

   w->redirected = TRUE;
   w->screen->overlayWindowCount--;
}
int
restackWindow (CompWindow *w, Ecore_X_Window aboveId)
{
   Ecore_X_Window_Type type;
   CompScreen *s;
   DE(("restackWindow aboveId: %x\n", aboveId));
   DE(("AboveWindow: name:%s role:%s\n",ecore_x_icccm_title_get(aboveId),ecore_x_icccm_window_role_get(aboveId)));
   type = composite_win_determine_type(aboveId);
   DE(("Windowtype: %d\n", type));
   
   s = w->screen;
   
   /* This put appplications menu on top */
   if (type == ECORE_X_WINDOW_TYPE_UNKNOWN)
   {   
      s->windows = eina_list_remove(s->windows, w);
      s->windows = eina_list_append(s->windows, w);
      return 1;
   }
   
   CompWindow *prev = findWindowAtScreen(s, aboveId);
   if (!prev)
   {
      s->windows = eina_list_remove(s->windows, w);
      s->windows = eina_list_append(s->windows, w);
   }
   else
   {
      s->windows = eina_list_remove(s->windows, w);
      s->windows = eina_list_append_relative(s->windows, w, prev);
   }

   return 1;
}

Ecore_X_Window_Type
composite_win_determine_type(Ecore_X_Window w)
{
   Ecore_X_Window *children = NULL;
   int nchildren, i;
   Ecore_X_Window_Type type;

   ecore_x_netwm_window_type_get(w, &type);
   
   if (type != ECORE_X_WINDOW_TYPE_NORMAL)
      return type;

   if (!(children = ecore_x_window_children_get(w, &nchildren)))
      return ECORE_X_WINDOW_TYPE_NORMAL;

   for (i = 0; i < nchildren; i++)
   {
      type = composite_win_determine_type(children[i]);
      if (type != ECORE_X_WINDOW_TYPE_NORMAL)
         return type;
   }

   if (children)
      free((void *) children);

   return ECORE_X_WINDOW_TYPE_NORMAL;
}
